Borland Online And The Cobb Group Present:


March, 1995 - Vol. 2 No. 3

Windows Programming - Creating a Windows program stub

By David Reid

Have you ever accidentally tried to execute a program written for Microsoft Windows from the DOS command line? If so, the program probably responded with the message

This program requires Microsoft Windows

In addition, you may have encountered other Windows programs that, when you run them from the DOS command line, display the message

This program cannot be run in DOS mode

Finally, there are some Windows programs, such as the Microsoft Windows SETUP program, that you can execute under either DOS or Windows. However, like SETUP, these programs may have radically different appearances and capabilities depending on whether you run them from DOS or Windows.

In this article, we'll discuss Windows program stubs. Then, we'll show you how to use the STUB statement in your program's module-definition (DEF) file to control what happens when a user runs your Windows programs from DOS.

What's a stub?

As you know, Windows programs reside in files with the EXE extension­­the same extension DOS uses for executable files. However, the mechanisms by which Windows and DOS load and execute EXE files are radically different. The problem is that DOS will load and attempt to execute any file with the EXE extension, whether or not the data in the file corresponds to meaningful CPU instructions. If you need proof of this, create the bogus executable file CRASH.EXE by using the statement

DIR > CRASH.EXE

Then (if you dare) instruct DOS to run the CRASH.EXE program. Doing so will almost guarantee to lock up your PC.

After you restart your computer, you might wonder how Windows programs manage to avoid this pitfall. The first portion of any Windows program actually contains a DOS program­­a stub­­right where DOS expects it to be. Typically, stubs are very short­­just long enough to display a message and terminate. When you tell DOS to run the EXE file, the stub is the only executable code that DOS sees­­the Windows executable code resides safely in the remainder of the file.

STUB born

As you probably know, every Windows program you create requires a DEF file. The linker builds the final executable file based on the information you specify in this file. One of the items you can specify in a DEF file is the name of the DOS executable file you want your Windows program to use as the Windows loading program, or stub. Figure A shows SAMPLE.DEF, the DEF file for the Windows program SAMPLE.


Figure A - This is a typical Windows module-definition file.

 NAME         SAMPLE
DESCRIPTION  'Sample Windows Program'
EXETYPE      WINDOWS
STUB         'WINSTUB.EXE'
CODE         MOVEABLE DISCARDABLE
DATA         MOVEABLE MULTIPLE
HEAPSIZE     1024
STACKSIZE    5120
EXPORTS
  WndProc    @1
  AboutDlg   @2

To use the STUB statement, simply follow the STUB keyword with the name of a DOS executable file in single quotes. For example, the STUB statement in Figure A tells the linker to use the file WINSTUB.EXE as the DOS stub for the Windows program SAMPLE.

Incidentally, you can include drive and path specifiers in the stub's name if the EXE file resides outside of the current working directory. Furthermore, you can use only EXE files as stubs­­you can't use COM files. Moreover, there's no restriction on the size of the stub other than the amount of memory DOS has available to run the stub program.

For instance, if you want to, you could specify DOS 5's QBASIC.EXE program as the stub for a Windows program:

STUB    'C:\DOS\QBASIC.EXE'

The linker will copy all 248KB of QBASIC.EXE to the beginning of the executable file. When you run the resulting EXE file from DOS, you'll see the standard QBASIC interface on the screen.

The default stub

If you omit the STUB statement from the DEF file, the linker uses a default stub that's only a few lines of assembler code. If you use Debug, CodeView, or some other debugger to examine a Windows program containing the default stub, you'll see the assembler code shown in Figure B starting at offset 0x40 in the executable file.


Figure B - This is the assembler code for the default Windows stub.

 MOV  DX,0010h
PUSH CS
POP  DS
MOV  AH,09h
INT  21h
MOV  AX,4C01h
INT  21h
NOP
NOP
DB   'This program must be run under Microsoft Windows.'
DB   0Dh, 0Ah, '$'

The default stub, although very short, represents a complete DOS program. It uses interrupt 0x21 function 0x09 to display the message

This program must be run under Microsoft Windows.

then terminates with the return value 1.

In contrast, when you use the STUB statement to tell the linker to use an existing EXE file as the stub, the linker adds the entire stub program to the beginning of the Windows executable file. Needless to say, you'll normally want to use very small stub programs to avoid unnecessarily increasing the size of your Windows executable files.

Writing sub stubs

In most cases, the default provides all the functionality your Windows programs need: It tells the user that the EXE file is a Windows­­not a DOS­­program. However, sometimes you may need a Windows program to perform some alternate processing task when you run it from DOS.

For instance, suppose your Windows program needs to add commands to the AUTOEXEC.BAT, CONFIG.SYS, or WIN.INI file before it will run properly. You could place the code that makes these changes in a substitute stub, thus reducing the overhead of the Windows portion of the program.

WIN-ning programs

Another use of stubs is creating "self-starting" Windows programs. In other words, instead of having WINSTUB.EXE chastise the user for attempting to run the program under DOS, why not simply start Windows for the user, then automatically run the program? After all, isn't obeying the user's command (running the program) more user-friendly than explaining why you can't obey the command?

Now let's build a simple stub that will make any Windows program self-starting. Inside this program, we'll begin by displaying a prompt to ask the user whether to run Windows automatically. If the user wants to launch Windows, we'll transfer all command-line arguments to a new command line, then use the system() function to pass this new command line to a secondary copy of the command processor, where we'll run Windows.

To begin, launch Borland C++ 4.0. When the main window of the Integrated Development Environment (IDE) appears, choose New from the File menu. In the editor window that appears, enter the code from Listing A.


Listing A: RUNME.C

#include <string.h>
#include <process.h>
#include <stdio.h>

void _ExceptionInit(void) {}

char CommandLine[128] = "WIN";

void main( int argc, char **argv )
{ char c;
  int i;

  puts("Requires Windows. Run Windows now (Y or N)?"); 
  c = getchar(); 

  if((c == 'y') || (c == 'Y'))
  { for(i = 0; 
        i < argc; 
        i++ )
    { strcat( CommandLine, " " );
      strcat( CommandLine, argv[i] );
    }
  system( CommandLine );
  }

When you finish entering the code, choose Save from the File menu. In the Save File As dialog box, enter RUNME.C in the File Name entry field.

Next, right-click on the editor window and choose Edit Local Options from the pop-up menu. In the Options dialog box, expand the Linker topic, select the General sub-topic, and then deselect the Include Debug Information check box. Click OK to save this change.

When the editor window reappears, right-click on it again and choose Target Expert from the pop-up menu. In the TargetExpert dialog box, choose Application in the Target Type list box, choose DOS Standard in the Platform list box, choose Small from the Target Model combo box, and deselect all the Standard Library check boxes except Runtime. Click OK to save this change.

When you're ready to build the stub application, choose Build All from the Project menu. When the compiler finishes building the application, click OK. Then you can add it as a stub for any Windows application you're building.

Most of the logic in RUNME concerns building the new command line from the information in the argv array. Although Windows programs don't use command-line arguments nearly as often as DOS programs do, you should still pass them on to the Windows program if the user supplies them. To build the command line properly, you need to shift all command-line arguments one position to the right and add the command WIN to the beginning of the command line.

For example, suppose the name of your Windows program is TESTPROG and the user types the following command at the DOS command line:

TESTPROG A: /S

To start Windows and run TESTPROG without losing the two command-line arguments, the command line you pass to system() must look like this:

WIN TESTPROG A: /S

Fortunately, argv[0] always contains the full path specification for the current program's EXE file. Therefore, you don't need to hardcode the program's name anywhere in RUNME.C. Once you've compiled RUNME and generated the file RUNME.EXE, you can use the statement

STUB   'RUNME.EXE'

in the DEF file of any Windows program.

Also, we included additional logic in the RUNME program to prompt the user with

Requires Windows. Run Windows now (Y or N)?

This extra step will probably please many users, since it takes anywhere from 10 to 30 seconds (depending on the speed of your machine) to get back to DOS if it turns out the user didn't really want to start Windows.

RUNME into Windows

To test RUNME, let's build a minimal Windows application and use RUNME.EXE as the stub program. To begin, close the RUNME.C editor window by double-clicking on its System menu icon.

Now, choose New Project... from the Project menu. In the New Project dialog box, enter \RUNME\RUNME.IDE in the Project Path And Name entry field, choose EasyWin from the Target Type list box, and select the Class Library check box from the Standard Libraries group.

To change the initial files that the IDE adds to the project, click the Advanced button. In the Advanced Options dialog box, select the CPP Node radio button and the DEF check box, and then click OK. When the New Project window reappears, click OK.

Double-click on the name runme [.cpp] in the Project window. When the editing window for this file appears, enter

#include <iostream.h>
void main() { cout << "RUNME Works!";}

When you finish, choose Save from the File menu.

Next, double-click on the name runme [.def] in the Project window. When the editing window for this file appears, enter

EXETYPE WINDOWS
STUB 'RUNME.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE DISCARDABLE
HEAPSIZE 4096
STACKSIZE 8192

Choose Save from the File menu, and then double-click on this editor window's System menu icon to close it. To build the application, choose Build All from the Project menu.

When the IDE finishes building the application, click OK, choose Exit from the File menu to quit the IDE, and then exit Windows. When the DOS prompt reappears, make the RUNME program's directory the current directory, and enter RUNME.

When the program asks if you want to run Windows now, enter Y. After loading Windows, you'll see the RUNME application's screen appear with the text RUNME Works! in the upper-left corner. Double-click on this window's System menu icon to close it.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.